# coding=utf-8
import json
from datetime import datetime

import falcon
from playhouse.shortcuts import model_to_dict
from falcon.media.validators import jsonschema

from db.models import SSHServer, OSBaseInfo
from libs.ssh import RemoteHost
from db.base import db

post_schema = {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "SSH Connection Details",
    "type": "object",
    "properties": {
        "ipaddress": {
            "type": "string",
            "format": "ipv4",
            "description": "The IP address of the server to connect to."
        },
        "user": {
            "type": "string",
            "description": "The username for authentication."
        },
        "port": {
            "type": "integer",
            "minimum": 1,
            "maximum": 65535,
            "description": "The port number for the SSH connection."
        },
        "password": {
            "type": "string",
            "description": "The password for authentication."
        },
        "desc": {
            "type": "string",
            "description": ""
        }
    },
    "required": ["ipaddress", "user", "port", "password"]
}

patch_schema = {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "SSH Connection Details",
    "type": "object",
    "properties": {
        "user": {
            "type": "string",
            "description": "The username for authentication."
        },
        "port": {
            "type": "integer",
            "minimum": 1,
            "maximum": 65535,
            "description": "The port number for the SSH connection."
        },
        "password": {
            "type": "string",
            "description": "The password for authentication."
        },
        "desc": {
            "type": "string",
            "description": ""
        }
    },
    "required": ["user", "port", "password"]
}


class SSHServerResource:

    def on_get(self, req: falcon.Request, resp: falcon.Response, resource_id: int = 0):
        if resource_id == 0:
            page = req.get_param_as_int("page", default=1)
            page_size = req.get_param_as_int("page_size", default=10)
            total = SSHServer.select().count()
            resources = SSHServer.select(SSHServer, OSBaseInfo) \
                .join(OSBaseInfo, join_type="LEFT OUTER JOIN", on=(SSHServer.id == OSBaseInfo.ssh_server_id)) \
                .columns(SSHServer.id, SSHServer.ipaddress, SSHServer.user, SSHServer.password, SSHServer.port,
                         SSHServer.desc, OSBaseInfo.hostname, OSBaseInfo.os_release) \
                .order_by(-SSHServer.created_time).paginate(page, page_size)

            resp.media = {
                "total": total,
                "page": page,
                "page_size": page_size,
                "data": list(resources.dicts())
            }
        else:
            resource = SSHServer.get_by_id(resource_id)
            try:
                os_info = OSBaseInfo.get(OSBaseInfo.ssh_server_id == resource_id)
            except OSBaseInfo.DoesNotExist:
                os_info = create_or_update_os_info(resource)

            os_info = model_to_dict(os_info)
            os_info["cpu"] = json.loads(os_info["cpu"])
            os_info["filesystem"] = json.loads(os_info["filesystem"])
            os_info["memory"] = json.loads(os_info["memory"])
            os_info["nics"] = json.loads(os_info["nics"])

            data = model_to_dict(resource)
            data.update(os_info)
            resp.media = data

    @jsonschema.validate(req_schema=post_schema)
    def on_post(self, req: falcon.Request, resp: falcon.Response):
        media = req.get_media()
        try:
            with db.atomic():
                new_server = SSHServer()
                new_server.ipaddress = media.get("ipaddress")
                new_server.user = media.get("user")
                new_server.password = media.get("password")
                new_server.port = media.get("port")
                new_server.desc = media.get("desc", "")
                new_server.save()

                # 新加 server 后，立即去采集一次
                create_or_update_os_info(new_server)
        except Exception as e:
            raise falcon.HTTPBadRequest(title=str(e), description="")

    def on_delete(self, req: falcon.Request, resp: falcon.Response, resource_id: int):
        SSHServer.delete_by_id(resource_id)
        OSBaseInfo.delete().where(OSBaseInfo.ssh_server_id == resource_id).execute()

    @jsonschema.validate(req_schema=patch_schema)
    def on_patch(self, req: falcon.Request, resp: falcon.Response, resource_id: int):
        media = req.get_media()
        try:
            resource = SSHServer.get_by_id(resource_id)
        except SSHServer.DoesNotExist:
            raise falcon.HTTPBadRequest(title="Resource not exist", description=resource_id)

        resource.user = media.get("user")
        resource.password = media.get("password")
        resource.port = media.get("port")
        resource.desc = media.get("desc")
        resource.updated_time = datetime.now()
        resource.save()

    def on_get_collect(self, req: falcon.Request, resp: falcon.Response, resource_id: int):
        server = SSHServer.get_by_id(resource_id)
        create_or_update_os_info(server)

    def on_get_shutdown(self, req: falcon.Request, resp: falcon.Response, resource_id: int):
        server = SSHServer.get_by_id(resource_id)
        remote_host = RemoteHost.from_param(server.ipaddress, server.port, server.user, server.password)
        remote_host.exec_command("shutdown -h now")
        remote_host.close()

    def on_get_restart(self, req: falcon.Request, resp: falcon.Response, resource_id: int):
        server = SSHServer.get_by_id(resource_id)
        remote_host = RemoteHost.from_param(server.ipaddress, server.port, server.user, server.password)
        remote_host.exec_command("reboot")
        remote_host.close()


def create_or_update_os_info(server: SSHServer) -> OSBaseInfo:
    data = _collect_os(server.ipaddress, server.port, server.user, server.password)

    try:
        os_info = OSBaseInfo.get(OSBaseInfo.ssh_server_id == server.id)
        OSBaseInfo.update(**data).where(OSBaseInfo.ssh_server_id == server.id).execute()
        return os_info
    except OSBaseInfo.DoesNotExist:
        data["ssh_server_id"] = server.id
        return OSBaseInfo.create(**data)


def _collect_os(ipaddress, port, user, password) -> dict:
    data = {
        "hostname": "",
        "os_release": "",
        "serial_number": "",
        "system_uuid": "",
        "uptime": "",
        "memory": "{}",
        "nics": "[]",
        "filesystem": "[]",
        "cpu": "{}"
    }
    remote_host = RemoteHost.from_param(ipaddress, port, user, password)
    data["hostname"] = remote_host.hostname()
    data["os_release"] = remote_host.os_release()
    data["serial_number"] = remote_host.serial_number()
    data["system_uuid"] = remote_host.system_uuid()
    data["uptime"] = remote_host.uptime()
    data["memory"] = json.dumps(remote_host.memory())
    data["nics"] = json.dumps(remote_host.nics())
    data["filesystem"] = json.dumps(remote_host.filesystem())
    data["cpu"] = json.dumps(remote_host.cpu())
    remote_host.close()
    data["collect_time"] = datetime.now()
    return data
